package com.sanyou.utils;
import com.sanyou.registry.IFunction;
import com.sanyou.registry.LogParseFunctionFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @Author yongxin
 * @Description SpEL解析工具类
 */
public final class SpELParserUtils {
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
    private static final ThreadLocal<StandardEvaluationContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();

    public static String templateParse(String text, Method method, Object[] argsObj) {
        String[] parameterNames = DISCOVERER.getParameterNames(method);
        if (parameterNames == null) {
            return text;
        }
        // 由于存在业务操作执行前后都要进行解析的场景，所以使用ThreadLocal存储上下文的context，避免重复创建
        StandardEvaluationContext context = CONTEXT_THREAD_LOCAL.get();
        if (context != null) {
            return PARSER.parseExpression(text, new TemplateParserContext()).getValue(context, String.class);
        }
        context = new StandardEvaluationContext();
        // 把接口的所有参数都放到context中
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], argsObj[i]);
        }
        // 获取对象中的属性值时，使用第一个参数作为rootObject
        context.setRootObject(argsObj[0]);
        CONTEXT_THREAD_LOCAL.set(context);
        // 结合使用`模板`和`标准`的表达式解析
        return PARSER.parseExpression(text, new TemplateParserContext()).getValue(context, String.class);
    }

    public static void removeContext() {
        CONTEXT_THREAD_LOCAL.remove();
    }

    //@functionName(param0,param1)
    private static final char AT = '@';
    private static final char LEFT_BRACKET = '(';
    private static final char RIGHT_BRACKET = ')';
    private static final String WHITESPACE = " ";
    private static final String EMPTY = "";
    private static final String COMMA = ",";

    public static String functionParse(String text) {
        char[] chars = text.toCharArray();
        // 由于需要对占位的函数进行结果替换，需要新字符串，预先扩展一部分空间，避免循环中不必要的扩容操作。空间换时间
        StringBuilder builder = new StringBuilder(text.length() + (text.length() / 2));
        int offset = 0;
        for (int i = 0; i < chars.length; i++) {
            char ch = chars[i];
            if (AT != ch) {
                continue;
            }
            // 解析自定义函数
            int j = i + 1;
            while (j < chars.length && chars[j] != LEFT_BRACKET) {
                j++;
            }
            String funcName = doParse(chars, j, i);
            // 解析函数参数
            int l = j;
            int count = 0;
            while (j < chars.length) {
                if(chars[j] == LEFT_BRACKET)
                    count++;
                else if(chars[j] == RIGHT_BRACKET) {
                    if (count == 0) break;
                    else count--;
                }
                j++;
            }
            String params = doParse(chars, j, l);
            IFunction<Object> function = LogParseFunctionFactory.FUNCTION_MAP.get(funcName.trim());
            if (function == null) {
                continue;
            }
            // 替换空白，使用apache lang包的replace方法，性能更佳
            String actualParams = StringUtils.replace(params, WHITESPACE, EMPTY);
            // 执行动态函数
            Object funcResult = function.apply(actualParams.split(COMMA));
            // 替换动态函数为执行的结果
            String replacement = (funcResult == null ? "" : funcResult.toString());
            builder.append(text, offset, i).append(replacement);
            offset = j + 1;
        }
        builder.append(text, offset, text.length());
        return builder.toString();
    }

    private static String doParse(char[] chars, int x, int y) {
        byte[] bytes = new byte[x - 1 - y];
        for (int k = 0; k < (x - 1 - y); k++) {
            bytes[k] = (byte) chars[y + 1 + k];
        }
        return new String(bytes);
    }
}
